﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;
using System.Collections;
using System.Runtime.CompilerServices;

namespace ForumLibrary.Data
{
    public abstract class ForumManager : MarshalByRefObject, IObservable
    {
        private static ForumData forumData = ForumData.getInstance();
        protected static DataManager _db = new DataManager();
        protected static hashEncode _encode = new hashEncode();
        protected static Logger log = forumData.logger;
        protected static ILog _logger = log.getLogger();
        protected List<ForumUser> _users = forumData.Users;
        protected const char _delimiter = '~';
        private List<Hashtable> _refreshFlags = forumData.RefreshFlags;
        private Hashtable _notifyFlags = forumData.NotifyFlags;
        protected const string _moderatorPassword = "Cuda";

        public static string ModeratorPassword { get { return _moderatorPassword; } }
        public static hashEncode Encoder { get { return _encode; } }

        public string register(string firstName, string lastName, string username, string password, string email, string phone, bool isModerator)
        {
            string[] parameters = new string[] { firstName, lastName, username, password };
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (!isModerator)
                {
                    if (_db.userExists(username))
                    {
                        _logger.Debug("Username " + username + " is not available");
                        return "Username is not available";
                    }
                }
                else
                {
                    if (_db.moderatorExists(username))
                    {
                        _logger.Debug("Username " + username + " is not available");
                        return "Username is not available";
                    }
                }
            }
            else
            {
                return checkResult;
            }

            //string maskedPassword = _encode.HashEncode(password);
            _db.addUser(username, password, email, phone, firstName, lastName, isModerator);
            _logger.Info("A user registered with the username: " + username);
            return "Registration Complete";
        }

        public string login(string username, string password)
        {
            string[] parameters = new string[] { username, password };
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(username))
                {

                    if (password.Equals(_db.getUserPassword(username).ToLower()))
                    {
                        changeUserLoginState(username, true);
                        _logger.Info("The user " + username + " logged in");
                        if (_db.moderatorExists(username))
                        {
                            initUserFlags(username, false); //set flags to false
                            return "Login Complete: Welcome Moderator " + username;
                        }
                        else
                        {
                            initUserFlags(username, false); //set flags to false
                            return "Login Complete: Welcome " + username;
                        }
                    }
                    else
                    {
                        _logger.Debug("The user " + username + " tried to login with the wrong password");
                        return "Password incorrect";
                    }
                }
                else
                {
                    return "Username doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public string logout(string username)
        {
            string[] parameters = new string[] { username };
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(username))
                {
                    changeUserLoginState(username, false);
                    _logger.Info("The user " + username + " logged out");
                    //initUserFlags(username, false);
                    return "Logout successfully";
                }
                else
                {
                    return "Username doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public string addFriend(string friendAdding, string friendAdded)
        {
            string[] parameters = new string[] { friendAdding, friendAdded };
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(friendAdding) && _db.userExists(friendAdded))
                {
                    if (!_db.isFriend(friendAdding, friendAdded))
                    {
                        _db.addFriend(friendAdding, friendAdded);
                        _logger.Info("The user " + friendAdding + " Added " + friendAdded + " as a friend");
                        return "Friend added successfully";
                    }
                    else
                    {
                        return friendAdding + " is already your friend";
                    }
                }
                else
                {
                    _logger.Debug("The user " + friendAdding + " failed to add " + friendAdded + " as a friend.");
                    return "Friend doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public string removeFriend(string friendRemoving, string friendRemoved)
        {
            string[] parameters = new string[] { friendRemoving, friendRemoved };
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(friendRemoving) && _db.userExists(friendRemoved))
                {
                    _db.removeFriend(friendRemoving, friendRemoved);
                    _logger.Info("The user " + friendRemoving + " removed " + friendRemoved + " from friends list");
                    return "Friend removed successfully";
                }
                else
                {
                    _logger.Debug("The user " + friendRemoving + " failed to remove " + friendRemoved + " from friends list.");
                    return "Friend doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public string addThread(string username, string forumName, string header, string body)
        {
            string[] parameters = new string[] { username, forumName, header, body };
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(username))
                {
                    if (_db.forumExists(forumName))
                    {
                        if (_db.threadExists(forumName, header))
                            return "Thread already exist, choose unique name";

                        _db.addThread(username, forumName, header, body);
                        setFlags(1); //_refreshFlags[1][username] = true;//username thread data refresh set
                        string message = username + " Added the thread " + header + " to the Forum " + forumName;
                        notifyAllUsers(_users, Message.ThreadAdded, username, forumName, header);
                        notifyFriends(_users, Message.Notification, message, username, forumName, header);
                        _logger.Info("The user " + username + " added the thread " + "\"" + header + "\"" + " to the forum " + forumName);
                        //addPost(username, forumName, header, header, body);
                        return "Thread added successfully";
                    }
                    else
                    {
                        return "Forum doesn't exist";
                    }
                }
                else
                {
                    return "User doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public string editThread(string username, string forumName, string threadName, string header, string body)
        {
            string[] parameters = new string[] { username, forumName, threadName, header, body };
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(username))
                {
                    if (_db.forumExists(forumName))
                    {
                        if (_db.threadExists(forumName, threadName))
                        {
                            if (_db.getThreadOwner(forumName, threadName).Equals(username))
                            {
                                if (threadName != header && _db.threadExists(forumName, header))
                                {
                                    return "Thread already exist, choose unique name";
                                }
                                _db.editThread(username, forumName, threadName, header, body);
                                setFlags(1); //_refreshFlags[1][username] = true;//username thread data refresh set
                                notifyAllUsers(_users, Message.ThreadEdited, username, forumName, threadName);
                                string message = username + " Edited the thread " + threadName + " in the Forum " + forumName;
                                notifyFriends(_users, Message.Notification, message, username, forumName, threadName);
                                _logger.Info("The user " + username + " edited the thread " + "\"" + header + "\"" + " in the forum " + forumName);
                                //editPost(username, forumName, threadName, threadName, header, body);
                                return "Thread edited successfully";
                            }
                            else
                            {
                                _logger.Debug("The user " + username + " failed to edit the thread " + "\"" + header + "\"" + " in the forum " + forumName);
                                return "Cannot edit a thread that you didn't create";
                            }
                        }
                        else
                        {
                            return "Thread doesn't exist";
                        }
                    }
                    else
                    {
                        return "Forum doesn't exist";
                    }
                }
                else
                {
                    return "User doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public string deleteThread(string username, string forumName, string threadName)
        {
            string[] parameters = new string[] { username, forumName, threadName };
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.moderatorExists(username))
                {
                    if (_db.forumExists(forumName))
                    {
                        if (_db.threadExists(forumName, threadName))
                        {
                            _db.deleteThread(username, forumName, threadName);
                            setFlags(1); //_refreshFlags[1][username] = true;//username thread data refresh set
                            _logger.Info("The user " + username + " deleted the thread " + "\"" + threadName + "\"" + " from the forum " + forumName);
                            return "Thread removed successfully";
                        }
                        else
                        {
                            return "Thread doesn't exist";
                        }
                    }
                    else
                    {
                        return "Forum doesn't exist";
                    }
                }
                else
                {
                    return "User isn't moderator and can't remove threads";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public string addPost(string username, string forumName, string threadName, string header, string body)
        {
            string[] parameters = new string[] { username, forumName, threadName, header, body };
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(username))
                {
                    if (_db.forumExists(forumName))
                    {
                        if (_db.threadExists(forumName, threadName))
                        {
                            if (_db.postExists(forumName, threadName, header))
                                return "Post already exist, choose unique name";
                            
                            _db.addPost(username, forumName, threadName, header, body);
                            setFlags(2); //_refreshFlags[2][username] = true;//username post data refresh set
                            notifyAllUsers(_users, Message.PostAdded, username, forumName, threadName);
                            notifyThreadCreator(_users, Message.Notification, username, forumName, threadName);
                            string message = username + " Added a post in the thread " + threadName + " in the forum " + forumName;
                            notifyFriends(_users, Message.Notification, message, username, forumName, threadName);
                            _logger.Info("The user " + username + " added the post " + "\"" + header + "\"" +
                                    " in the thread " + threadName + " in the forum " + forumName);
                            return "Post Added successfully";
                        }
                        else
                        {
                            return "Thread doesn't exist";
                        }
                    }
                    else
                    {
                        return "Forum doesn't exist";
                    }
                }
                else
                {
                    return "User doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public string editPost(string username, string forumName, string threadName, string postName, string newHeader, string newBody)
        {
            string[] parameters = new string[] { username, forumName, threadName, postName, newHeader, newBody };
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(username))
                {
                    if (_db.forumExists(forumName))
                    {
                        if (_db.threadExists(forumName, threadName))
                        {
                            if (_db.postExists(forumName, threadName, postName))
                            {
                                if (_db.getPostOwner(forumName, threadName, postName).Equals(username))
                                {
                                    if (postName != newHeader && _db.postExists(forumName, threadName, newHeader))
                                    {
                                        return "Post already exist, choose unique name";
                                    }
                                    _db.editPost(username, forumName, threadName, postName, newHeader, newBody);
                                    setFlags(2); //_refreshFlags[2][username] = true;//username post data refresh set
                                    notifyAllUsers(_users, Message.PostEdited, username, forumName, threadName);
                                    _logger.Info("The user " + username + " edited the post " + "\"" + postName + "\"" +
                                        " in the thread " + threadName + " in the forum " + forumName);
                                    return "Post edited successfully";
                                }
                                else
                                {
                                    return "Cannot edit a post that you didn't create";
                                }
                            }
                            else
                            {
                                return "Post doesn't exist";
                            }
                        }
                        else
                        {
                            return "Thread doesn't exist";
                        }
                    }
                    else
                    {
                        return "Forum doesn't exist";
                    }
                }
                else
                {
                    return "User doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public string deletePost(string username, string forumName, string threadName, string postName)
        {
            string[] parameters = new string[] { username, forumName, threadName, postName };
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(username))
                {
                    if (_db.forumExists(forumName))
                    {
                        if (_db.threadExists(forumName, threadName))
                        {
                            if (_db.postExists(forumName, threadName, postName))
                            {

                                if (_db.getPostOwner(forumName, threadName, postName).Equals(username) || _db.moderatorExists(username))
                                {
                                    _db.deletePost(username, forumName, threadName, postName);
                                    setFlags(2);// _refreshFlags[2][username] = true;//username post data refresh set
                                    notifyAllUsers(_users, Message.PostDeleted, username, forumName, threadName);
                                    _logger.Info("The user " + username + " deleted the post " + "\"" + postName + "\"" +
                                        " in the thread " + threadName + " in the forum " + forumName);
                                    return "Post removed successfully";
                                }
                                else
                                {
                                    return "Cannot delete a post that you didn't create";
                                }
                            }
                            else
                            {
                                return "Post doesn't exist";
                            }
                        }
                        else
                        {
                            return "Thread doesn't exist";
                        }
                    }
                    else
                    {
                        return "Forum doesn't exist";
                    }
                }
                else
                {
                    return "User doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        private void setFlags(int type)
        {
            List<string> keys = new List<string>();
            Hashtable table = _refreshFlags[type];
            foreach (System.Collections.DictionaryEntry de in table)
                keys.Add(de.Key.ToString());
            foreach (string key in keys)
                table[key] = true;
        }

        public abstract object searchForNewFriend(string username, string searchedUser);

        public abstract object getUser(string username);
        
        public abstract object getUsers();

        public abstract object getFriends(string username);

        public abstract object getForums();

        public abstract object getThreads(string forumName);

        public abstract object getPosts(string forumName, string threadName);

        public bool checkModeratorPassword(string pass)
        {
            return pass.Equals(_moderatorPassword);
        }

        private void changeUserLoginState(string username, bool state)
        {
            ForumUser client = _users.Find(user => user.Username.ToLower().Equals(username.ToLower()));
            if (client != null)
                client.LoginState = state;
        }

        public bool getChangedFlag(string frameType, string username)
        {
            int type = frameType.Equals("Threads") ? 1 : (frameType.Equals("Posts") ? 2 : 0);
            bool response =  (bool)_refreshFlags[type][username];
            _refreshFlags[type][username] = false; //reset flag
            return response;
        }

        public bool getNotifyFlag(string username)
        {
            if (username == null || username == "" || _notifyFlags == null || _notifyFlags.Count < 1)
                return false;
            bool response = (bool)_notifyFlags[username];
            _notifyFlags[username]= false;
            return response;
        }

        public void loadUsers()
        {
            _db.loadUsers(ref _users);
        }

        private void initUserFlags(string username, bool val)
        {
            try
            {
                _refreshFlags[0].Add(username, val);
                _refreshFlags[1].Add(username, val);
                _refreshFlags[2].Add(username, val);
                _notifyFlags.Add(username, val);
            }
            catch { }
        }

        private string checkValidParameters(string[] parameters)
        {
            string checkResult = "";
            foreach (string param in parameters)
                if (param.Contains(_delimiter))
                {
                    checkResult = "Fields cannot contain the special character ~";
                    break;
                }
            return checkResult;
        }

        public int lastNotificationsNum(string username)
        {
            int notsNum = 0;
            ForumUser user = _users.Find(u => u.Username.ToLower().Equals(username.ToLower()));
            if (user != null)
                notsNum = user.lastNotificationsNum();
            return notsNum;
        }
        
        public List<Notification> getUserNotifications(string username)
        {
            List<Notification> notifications = new List<Notification>();
            ForumUser user = _users.Find(u => u.Username.ToLower().Equals(username.ToLower()));
            if (user != null)
                notifications = user.Notifications;
            return notifications;
        }
        
        public void notifyFriends(List<ForumUser> users, Message messageType, string message, string username = "", string forumName = "", string threadName = "")
        {
            List<IObserver> controllers = new List<IObserver>();
            foreach (ForumUser user in users)
            {
                if (_db.isFriend(user.Username, username) && !user.Username.Equals(username))
                {
                    controllers.Add(user.Controller);
                    user.addNotification(messageType, message);
                    try
                    {
                        //_notifyFlags.Add(user.Username, true);
                        _notifyFlags[user.Username] = true;
                    }
                    catch { }
                }
            }
            new System.Threading.Thread(run).Start(new Notify(controllers, messageType, message, username, forumName, threadName));
        }

        public void notifyThreadCreator(List<ForumUser> users, Message messageType, string username = "", string forumName = "", string threadName = "")
        {
            List<IObserver> controllers = new List<IObserver>();
            string notification = username + " Added a post in your thread " + threadName + " in the forum " + forumName;
            string threadCreator = _db.getThreadOwner(forumName, threadName).ToLower();
            foreach (ForumUser user in users)
            {
                if (user.Username.ToLower().Equals(threadCreator) && !threadCreator.Equals(username.ToLower()))
                {
                    controllers.Add(user.Controller);
                    user.addNotification(messageType, notification);
                    break;
                }
            }
            new System.Threading.Thread(run).Start(new Notify(controllers, messageType, notification, username, forumName, threadName));
        }

        public void notifyAllUsers(List<ForumUser> users, Message messageType, string username = "", string forumName = "", string threadName = "")
        {
            List<IObserver> controllers = new List<IObserver>();
            foreach (ForumUser user in users)
            {
                controllers.Add(user.Controller);
            }
            new System.Threading.Thread(run).Start(new Notify(controllers, messageType, "", username, forumName, threadName));
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public void run(Object obj)
        {
            Notify n = (Notify)obj;
            foreach (IObserver user in n._controllers)
            {
                if (user != null)
                    try
                    {
                        user.update(n._message, n._notification, n._username, n._forumName, n._threadName);

                    }
                    catch (Exception)
                    {
                        //_logger.Error("no connection to user: " + user);
                        break;
                    }
            }
        }

        class Notify
        {
            public List<IObserver> _controllers;
            public Message _message;
            public string _notification;
            public string _username;
            public string _forumName;
            public string _threadName;

            public Notify(List<IObserver> controllers, Message message, string notification, string username = "", string forumName = "", string threadName = "")
            {
                _controllers = controllers;
                _notification = notification;
                _message = message;
                _username = username;
                _forumName = forumName;
                _threadName = threadName;
            }
        }

        public void demo()
        {
            //------------------------Create the forums localy on the server------------------------//
            _db.addForum("Movies");
            _db.addForum("Music");
            //------------------------Add some users for testing------------------------//
            register("Adiel", "1", "Ashrov", "123", "Adiel", "Ashrov", false);
            register("Etai", "1", "Hazan", "123", "Etai", "Hazan", false);
            register("Benny", "1", "Mihaeli", "123", "Benny", "Mihaeli", true);
            register("Chezi", "1", "Halpert", "123", "Chezi", "Halpert", false);
            //------------------------Add some threads for testing------------------------//
            _db.addThread("etai", "Movies", "Inception", "Best movie of 2010!");
            _db.addThread("chezi", "Movies", "The Dark Knight", "Best movie of 2008!");
            _db.addThread("etai", "Music", "Adele", "Adele's 19 and 21 are great albums!");
            _db.addThread("etai", "Movies", "Irish", "Irish music is owseme!");

            _db.addPost("benny", "Movies", "Inception", "Best movie of 2012!", "body");
        }

        /// <summary>
        /// Method to set that the remote object "IForumManager" would be
        /// available indefinitely and not disconnect after 5 minutes
        /// </summary>
        /// <returns>null to set indefinitely time window </returns>
        public override object InitializeLifetimeService()
        {
            //return base.InitializeLifetimeService();
            return null;
        }

        public void subscribe(IObserver observer, string username, bool loginState)
        {
            ForumUser client = _users.Find(u => u.Username.ToLower().Equals(username.ToLower()));
            ForumUser user = new ForumUser(username, observer, loginState);
            if (client == null)
                _users.Add(user);
            else if (client.Controller == null)
            {
                client.Controller = observer;
                client.LoginState = loginState;
            }
        }

        public void unsubscribe(string username)
        {
            ForumUser client = _users.Find(user => user.Username.Equals(username));
            if (client != null)
                _users.Remove(client);
        }

        //------------------------Methods only for ForumGUIManager-------------------------//
        public abstract bool isPostOwnerOrModerator(String forumName, String threadName, String PostName, String username);

        public abstract string getThreadDetails(string _forum, string header);

        public abstract string getPostDetails(string forum, string thread, string header);

    }
}
